@interface GCDAsyncSocket : NSObject
// 初始化方法
- (instancetype)init;
- (instancetype)initWithSocketQueue:(nullable dispatch\_queue\_t)sq;
- (instancetype)initWithDelegate:(nullable id\<GCDAsyncSocketDelegate\>)aDelegate delegateQueue:(nullable dispatch\_queue\_t)dq;
- (instancetype)initWithDelegate:(nullable id\<GCDAsyncSocketDelegate\>)aDelegate delegateQueue:(nullable dispatch\_queue\_t)dq socketQueue:(nullable dispatch\_queue\_t)sq;
// Accepting 方法:告诉socket开始监听并且从对应端口的连接上接收信息
- (BOOL)acceptOnPort:(uint16\_t)port error:(NSError \*\*)errPtr;
- (BOOL)acceptOnInterface:(nullable NSString \*)interface port:(uint16\_t)port error:(NSError \*\*)errPtr;
- (BOOL)acceptOnUrl:(NSURL \*)url error:(NSError \*\*)errPtr;
// Connect 方法:连接到对应的host上的端口(系统底层会启用TCP三次握手来确认连接成功)
- (BOOL)connectToHost:(NSString \*)host onPort:(uint16\_t)port error:(NSError \*\*)errPtr;
- (BOOL)connectToHost:(NSString \*)host
onPort:(uint16\_t)port
withTimeout:(NSTimeInterval)timeout
error:(NSError \*\*)errPtr; // 增加了过期时间
- (BOOL)connectToHost:(NSString \*)host
onPort:(uint16\_t)port
viaInterface:(nullable NSString \*)interface
withTimeout:(NSTimeInterval)timeout
error:(NSError \*\*)errPtr;
- (BOOL)connectToAddress:(NSData \*)remoteAddr error:(NSError \*\*)errPtr;
- (BOOL)connectToAddress:(NSData \*)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError \*\*)errPtr;
- (BOOL)connectToAddress:(NSData \*)remoteAddr
viaInterface:(nullable NSString \*)interface
withTimeout:(NSTimeInterval)timeout
error:(NSError \*\*)errPtr;
- (BOOL)connectToUrl:(NSURL \*)url withTimeout:(NSTimeInterval)timeout error:(NSError \*\*)errPtr;
// Disconnect 方法:断开连接是同步进行的,调用的时候就马上断开,任何挂起的read和write操作全部都会取消
- (void)disconnect;
- (void)disconnectAfterReading; // 可以等到所有的read操作完成后再断开
- (void)disconnectAfterWriting; // 同上,等write
- (void)disconnectAfterReadingAndWriting; // 不需要再解释了吧=。=
// Diagnostics 参数:判断一个socket是否断开连接还是在连接中,一个断开连接的socket其实是可以被重复利用,再次拿来连接和监听使用的。这里的部分全部是property,并不包含诊断所用的方法,但是这些参数是判断socket当前状态的关键
@property (atomic, readonly) BOOL isDisconnected;
@property (atomic, readonly) BOOL isConnected; // 当处于connect过程中时有可能两种状态都不是
@property (atomic, readonly, nullable) NSString \*connectedHost;
@property (atomic, readonly) uint16\_t connectedPort;
@property (atomic, readonly, nullable) NSURL \*connectedUrl;
@property (atomic, readonly, nullable) NSString \*localHost;
@property (atomic, readonly) uint16\_t localPort; // 如果disconnect,那么对应的端口或者url就应该是0或者nil
@property (atomic, readonly, nullable) NSData \*connectedAddress;
@property (atomic, readonly, nullable) NSData \*localAddress;
@property (atomic, readonly) BOOL isIPv4;
@property (atomic, readonly) BOOL isIPv6;
@property (atomic, readonly) BOOL isSecure;// socket是否被SSL/TLS保护
// Reading方法:read和write方法是不会阻塞的(异步)。当一个read完成的时候,socket:didReadData:withTag:方法会分发到delegateQueue上,同理write也有对应的方法分发。
- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag;// 开始读取socket中可读取的字节
- (void)readDataWithTimeout:(NSTimeInterval)timeout
buffer:(nullable NSMutableData \*)buffer
bufferOffset:(NSUInteger)offset
tag:(long)tag;
- (void)readDataWithTimeout:(NSTimeInterval)timeout
buffer:(nullable NSMutableData \*)buffer
bufferOffset:(NSUInteger)offset
maxLength:(NSUInteger)length
tag:(long)tag;// buffer是可以根据数据自动变化大小的
- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag;
- (void)readDataToLength:(NSUInteger)length
withTimeout:(NSTimeInterval)timeout
buffer:(nullable NSMutableData \*)buffer
bufferOffset:(NSUInteger)offset
tag:(long)tag;
- (void)readDataToData:(NSData \*)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
- (void)readDataToData:(NSData \*)data
withTimeout:(NSTimeInterval)timeout
buffer:(nullable NSMutableData \*)buffer
bufferOffset:(NSUInteger)offset
tag:(long)tag;
- (void)readDataToData:(NSData \*)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag;
- (void)readDataToData:(NSData \*)data
withTimeout:(NSTimeInterval)timeout
buffer:(nullable NSMutableData \*)buffer
bufferOffset:(NSUInteger)offset
maxLength:(NSUInteger)length
tag:(long)tag;
- (float)progressOfReadReturningTag:(nullable long \*)tagPtr bytesDone:(nullable NSUInteger \*)donePtr total:(nullable NSUInteger \*)totalPtr;
// Writing 方法:
- (void)writeData:(NSData \*)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
- (float)progressOfWriteReturningTag:(nullable long \*)tagPtr bytesDone:(nullable NSUInteger \*)donePtr total:(nullable NSUInteger \*)totalPtr;
// Security 方法:使用SSL/LTS来保证连接的安全性
- (void)startTLS:(nullable NSDictionary \<NSString\*,NSObject\*\>\*)tlsSettings;
// 一个方法,但是内容比较复杂,字典包含很多可选择的key,比如GCDAsyncSocketManuallyEvaluateTrust, - GCDAsyncSocketUseCFStreamForTLS, - kCFStreamSSLPeerName等
/\* 高级方法 :传统的socket在对话结束之前是不会关闭的,但是技术上可以让远端的点关闭write stream,然后本地的socket就会发现已经没有信息被读入了,但是它任然处于可以被写入信息的状态,并且远程端点仍然可以继续接受本地发送过去的信息。
所以需要能让本地的客户端能够主动关闭write stream,并且通知服务器端说“我们不会再发送更多信息过去了”。
更糟糕的是,从TCP的层面,并不能分清楚到底是一段read stream结束还是整个socket结束了,它们都会让TCP栈收到一个FIN包,唯一的区别方法只能是看有没有继续发送的数据(如果是socket结束了,服务器会发送一个RST包)。
为了解决这个问题,APPLE的方法是当read stream被关闭之后,系统API马上请求将socket也关闭,听起来很简单粗暴,但是实际上挺简单有用的。下面这些参数保证了这些逻辑
\*/
@property (atomic, assign, readwrite) BOOL autoDisconnectOnClosedReadStream;// 默认是YES,如果服务器是自己维护的,或许可以考虑改为no,并且完成socketDidCloseReadStream方法。
// GCDAsyncSocket通过一个内含的serial dispatch\_queue来维护现成安全,通常由实例本身来维护这个队列,然而也可能在初始化这个实例的时候就将队列作为参数交给它了。这样外部能通过操控socket的线程优点度来完成一些有技巧性的操作=。=但是也会带来一些死锁的烦恼,所以需要谨慎操作,比如当改变了一个实例的targetQueue,但是一些操作需要在以前的queue上执行的时候。这个问题主要怪GCD的API没有办法找到一个queue的targetQueue。
- (void)markSocketQueueTargetQueue:(dispatch\_queue\_t)socketQueuesPreConfiguredTargetQueue;
- (void)unmarkSocketQueueTargetQueue:(dispatch\_queue\_t)socketQueuesPreviouslyConfiguredTargetQueue;
- (void)performBlock:(dispatch\_block\_t)block;
- (int)socketFD;
- (int)socket4FD;
- (int)socket6FD;
\#if TARGET\_OS\_IPHONE
- (nullable CFReadStreamRef)readStream;
- (nullable CFWriteStreamRef)writeStream;
- (BOOL)enableBackgroundingOnSocket;
- (nullable SSLContextRef)sslContext;
// 辅助功能:一些辅助性的功能函数,全都是class method,并且推荐在background thread中使用
+ (nullable NSMutableArray \*)lookupHost:(NSString \*)host port:(uint16\_t)port error:(NSError \*\*)errPtr;
+ (nullable NSString \*)hostFromAddress:(NSData \*)address;
+ (uint16\_t)portFromAddress:(NSData \*)address;
+ (BOOL)isIPv4Address:(NSData \*)address;
+ (BOOL)isIPv6Address:(NSData \*)address;
+ (BOOL)getHost:( NSString \* \_\_nullable \* \_\_nullable)hostPtr port:(nullable uint16\_t \*)portPtr fromAddress:(NSData \*)address;
+ (BOOL)getHost:(NSString \* \_\_nullable \* \_\_nullable)hostPtr port:(nullable uint16\_t \*)portPtr family:(nullable sa\_family\_t \*)afPtr fromAddress:(NSData \*)address;
// 最后是delegate
@protocol GCDAsyncSocketDelegate \<NSObject\>
@optional
- (nullable dispatch\_queue\_t)newSocketQueueForConnectionFromAddress:(NSData \*)address onSocket:(GCDAsyncSocket \*)sock;// 在socket:didAcceptNewSocket:之前就会调用,为新接受的socket提供socketQueue,如果没有实现或者返回Nil那么新的socket就会自己创建默认queue
- (void)socket:(GCDAsyncSocket \*)sock didAcceptNewSocket:(GCDAsyncSocket \*)newSocket; // 当一个socket接受一个连接时调用,创建另外一个socket来处理这个连接。必须retian这个新的socket以免被release的时候丢失连接,这个新的socket默认使用相同的delegateQueue
- (void)socket:(GCDAsyncSocket \*)sock didConnectToHost:(NSString \*)host port:(uint16\_t)port;
- (void)socket:(GCDAsyncSocket \*)sock didConnectToUrl:(NSURL \*)url;
- (void)socket:(GCDAsyncSocket \*)sock didReadData:(NSData \*)data withTag:(long)tag;
- (void)socket:(GCDAsyncSocket \*)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag;
- (void)socket:(GCDAsyncSocket \*)sock didWriteDataWithTag:(long)tag;
- (void)socket:(GCDAsyncSocket \*)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag;
// 这些方法名上都很直观就是了=。=
- (NSTimeInterval)socket:(GCDAsyncSocket \*)sock shouldTimeoutReadWithTag:(long)tag
elapsed:(NSTimeInterval)elapsed
bytesDone:(NSUInteger)length;// 如果一个read操作在超时之后还没有完成,这个方法能让用户自由拓展超时时间,注意一个操作有可能调用这个函数很多次,elapsed是超过的总时间
- (NSTimeInterval)socket:(GCDAsyncSocket \*)sock shouldTimeoutWriteWithTag:(long)tag
elapsed:(NSTimeInterval)elapsed
bytesDone:(NSUInteger)length; // 同上,这里是write操作的超时处理
- (void)socketDidCloseReadStream:(GCDAsyncSocket \*)sock; //当read stream被关闭但是write stream仍然可以操作时
- (void)socketDidDisconnect:(GCDAsyncSocket \*)sock withError:(nullable NSError \*)err;
- (void)socketDidSecure:(GCDAsyncSocket \*)sock; // 当SSL/TLS诊断完成后调用(如果没有成功socket会直接关闭)
- (void)socket:(GCDAsyncSocket \*)sock didReceiveTrust:(SecTrustRef)trust
completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler;